/********************************************************************************************************************************
 * 《深入Linux设备驱动程序内核机制》-- 陈学松 著，电子工业出版社， 2012年1月第1次印刷
 * Date: 2024-04-17 11:05
 * Author: dimon.chen 
 * EMali: 1181302388@qq.com 
 * 
 * 章节: 第二章 内核内存管理
 *       
 * 
 ********************************************************************************************************************************/

//Linux 内核模块总是应该包含着3个头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#include <linux/slab.h>
#include <linux/mm.h>


//使用内核函数vmalloc()/vfree()需要包含头文件
#include <linux/vmalloc.h>

//使用 current 宏获取当前进程的 threadinfo 结构体
#include <linux/sched.h>



//**********************************************************************
// kmalloc() test
// 测试 kamlloc() 内核内存分配函数()
// 1. 使用 kmalloc() 申请内存
// 2. 适应GFP_KERNEL, GFP_ATOMIC
// 3. 使用 virt_to_phys() 函数把内核虚拟地址（线性映射区）转换为物理地址
// 4. virt_to_phys() 智能用于内核地址现象映射区或者alloc_pages()
//**********************************************************************

void *ptr1 = NULL;

//测试内核空间地址分配函数 kmalloc()
//kmalloc()从内核线性地址映射区分配连续的地址（内存物理地址上是连续的，
//虚拟地址空间上也是连续的）。
//kmalloc() 依赖于 SLAB 物理内存分配机制
//SLAB物理内存分配机制，提前申请了物理页，并把物理页组织成大小相同的小对象
//当kalloc()申请小的对象内存时，直接从SLAB缓冲池中获取内存，当SLAB缓冲池中没有
//内存时，SLAB才会通过内存管理 Buddy 伙伴管理系统，从物理地址线性映射区ZONE_NORMAL
//申请物理页
//

static int alloc_kmalloc_init(void)
{
    size_t size = 1024; //allocate 1024 bytes
    ptr1 = kmalloc(size, GFP_KERNEL);
    if(!ptr1){
        //handle error
        printk("memory allocation failed\n");
        return -ENOMEM;
    }
    else{
        printk("alloc memory successfully!\n");
    }


    printk("alloc kernel Virtual address:%p\n", ptr1);
    unsigned long phyAddr = virt_to_phys(ptr1);
    printk("alloc kernel Virtual address:%08lx\n", phyAddr);

    return 0;
}

void alloc_kmalloc_exit(void)
{
    if(ptr1){
        kfree(ptr1);
        printk("kfree() to free kernel virtual address:%p\n", ptr1);
    }
        
    ptr1 = NULL;
}



//**********************************************************************
// vmalloc() test
// 测试 vmalloc() 内核接口函数申请物理页
// 1. 使用 vmalloc() 申请内存
// 2. 适应GFP_KERNEL, GFP_ATOMIC
// 3. vmalloc()申请到的地址是虚拟地址连续的，物理地址不一定连续
// 4. vmlloc() 需要使用 vfree()
//
//**********************************************************************

void *ptr2 = NULL;

static int my_vmalloc_init(void)
{
    ptr2 = vmalloc(1024*8);

    if(!ptr2){
        printk("vmalloc fail\n");
        return -ENOMEM;
    }
    else{
        printk("vmalloc() success\n");
    }

    printk("vmalloc virtual address :%p\n", ptr2);

    return 0;
}

void my_vmalloc_exit(void)
{
    if(ptr2){
        vfree(ptr2);
        printk("vfree() to free kernel virtual address:%p\n", ptr2);
    }
        
    ptr2 = NULL;
}


//**********************************************************************
// alloc_pages() test
// 测试 alloc_pages() 内核接口函数申请物理页
// 1. 使用 kmalloc() 申请内存
// 2. 适应GFP_KERNEL, GFP_ATOMIC
// 3. 使用 virt_to_phys() 函数把内核虚拟地址（线性映射区）转换为物理地址
//
//**********************************************************************


int  my_module_init(void)
{
    printk("Hello module init\n");
    printk("current taskinfo: %s\n", current->comm);

    alloc_kmalloc_init();
    my_vmalloc_init();

    return 0;
}

void  my_module_exit(void)
{
    printk("Hello module deinit\n");
    alloc_kmalloc_exit();
    my_vmalloc_exit();
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("dimon.chen 1181302388@qq.com");